package ar.uba.is2.ec.config;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Properties;

import org.apache.log4j.Logger;

import ar.uba.is2.common.Const;
import ar.uba.is2.common.config.AbstractConfiguracion;
import ar.uba.is2.common.loc.Coordenada;
import ar.uba.is2.common.mensaje.Componente;
import ar.uba.is2.common.mensaje.Receptor;
import ar.uba.is2.common.mensaje.TipoMensaje;
import ar.uba.is2.common.mensaje.canal.Canal;
import ar.uba.is2.common.mensaje.canal.impl.CanalImpl;
import ar.uba.is2.common.mensaje.contenido.Identificador;
import ar.uba.is2.common.mensaje.protocolo.Protocolo;
import ar.uba.is2.common.mensaje.protocolo.impl.ProtocoloSimple;
import ar.uba.is2.common.mensaje.protocolo.impl.SterenProtocol;
import ar.uba.is2.common.mensaje.puerto.impl.PuertoArchivoFactory;
import ar.uba.is2.ec.comm.ModuloComunicacion;
import ar.uba.is2.ec.comm.impl.ModuloComunicacionImpl;
import ar.uba.is2.ec.matem.ModelRunner;
import ar.uba.is2.ec.matem.dao.impl.MedicionDaoImpl;
import ar.uba.is2.ec.matem.exec.Ejecutor;
import ar.uba.is2.ec.matem.exec.impl.DespachadorSimple;
import ar.uba.is2.ec.matem.exec.impl.EjecutorConcurrente;
import ar.uba.is2.ec.matem.modelo.ContextoDeModelo;
import ar.uba.is2.ec.matem.modelo.ModeloMatematico;
import ar.uba.is2.ec.matem.motor.Regla;
import ar.uba.is2.ec.receptor.EstacionCentralReceptor;
import ar.uba.is2.ec.scmanager.ReceptorSuscripcionDeDatos;
import ar.uba.is2.ec.scmanager.SuscriptorDeDatosExternos;
import ar.uba.is2.ec.trmanager.ReceptorHeartbit;
import ar.uba.is2.ec.trmanager.ReceptorPersistenciaMensaje;

public class Configuracion extends AbstractConfiguracion {
	
	private static final Logger logger = Logger.getLogger(Configuracion.class);
	
	private static final String LOG4J_FILENAME = "log4j-ec.properties";

	private static final String archivoConfiguracion = "ec.properties";

	@Override
	protected void doInit() {
		
		configurarContexto();
		
		configurarEC();
		
		configurarModuloComunicacionTR();
		
		configurarBus();

		configurarModuloComunicacionSC();
		
		configurarModelosMatematicos();
		
	}

	private void configurarEC() {
		
		ConfiguracionGeneralEC conf = new ConfiguracionGeneralEC();
		
		String nombreEC = Contexto.instance.get("ec.nombre");
		
		conf.setComponenteEc(new Componente(nombreEC));
		
		double latitud = Double.parseDouble(Contexto.instance.get("ec.latitud"));
		double longitud = Double.parseDouble(Contexto.instance.get("ec.longitud"));
		
		conf.setCoordenada(new Coordenada(latitud, longitud));

		String string = Contexto.instance.get("ec.terminales.remotas.adminitradas");
		String[] trIds = string.split(",");
		
		List<Componente> trAdministradas = new ArrayList<Componente>();
		for (String trId : trIds) {
			trAdministradas.add(new Componente(trId));
		}
		
		conf.setTrAdministradas(trAdministradas);
		
		Contexto.instance.setConfiguracionGeneralEC(conf);

	}

	private Protocolo configurarProtocoloSC() {
		ProtocoloSimple protocol = new ProtocoloSimple();
		
		CanalImpl canal = new CanalImpl();
		canal.setPuertoFactory(new PuertoArchivoFactory("SC"));
		canal.suscribirSalida(Const.COMPONENTE_SERVIDOR_CENTRAL);
		canal.suscribirEntrada(Contexto.instance.getConfiguracionGeneralEC().getComponenteEc(), protocol);
		
		protocol.setCanal(canal);
		
		Contexto.instance.setProtocoloSC(protocol);
		return protocol;
	}

	@Override
	protected String getLoggingFilename() {
		return LOG4J_FILENAME;
	}

	private void configurarBus() {
		
		// Receptor heartbit: registra las TR activas y caidas 
		ReceptorHeartbit receptorHeartbit = new ReceptorHeartbit();
		
		Contexto.instance.getModuloComunicacion().registrarReceptor(receptorHeartbit);
		
		// Receptor persistencia: persiste los mensajes recibidos
		MedicionDaoImpl dao = new MedicionDaoImpl();
		
		ReceptorPersistenciaMensaje receptorPersistencia = new ReceptorPersistenciaMensaje(dao);
		
		Contexto.instance.getModuloComunicacion().registrarReceptor(receptorPersistencia, TipoMensaje.DATOS_SENSADOS);
		
		// Receptor dummy: avisa de la llegada de mensajes
		Contexto.instance.getModuloComunicacion().registrarReceptor(EstacionCentralReceptor.instance);
	}

	private void configurarContexto() {
		try {
			logger.info("Levantando propiedades");
			Contexto contexto = new Contexto();
			
			Properties propiedades = new Properties();
			propiedades.load(ClassLoader.getSystemResourceAsStream(archivoConfiguracion));
			contexto.setPropiedades(propiedades);
			
			Contexto.instance = contexto;
			
		} catch (Exception e) {
			throw new RuntimeException("No se pudo terminar la configuracion", e);
		}
	}

	private void configurarModelosMatematicos() {
		logger.info("Configurando Modelos Matemáticos");
		
		Collection<Ejecutor> configurarEjecutores = configurarEjecutores();
		
		for (Ejecutor ejecutor : configurarEjecutores) {
			DespachadorSimple.getInstance().registrarEjecutor(ejecutor);
		}
		
		Collection<ModeloMatematico> modelos = ModeloMatematicoFactory.instance.obtenerModelos();
		
		suscribirADatosRequeridos(modelos);
		
		for (ModeloMatematico modelo : modelos) {
			ModelRunner modelRunner = new ModelRunner(modelo, new ContextoDeModelo());
			
			modelRunner.correr();
		}
		
	}

	private void suscribirADatosRequeridos(Collection<ModeloMatematico> modelos) {
		for (ModeloMatematico modelo : modelos) {
			Collection<Regla> reglas = modelo.getReglas();
			
			for (Regla regla : reglas) {
				Collection<Identificador> datos = regla.getDatosRequeridos();
				
				for (Identificador dato : datos) {
					boolean mismaEc = dato.getCoordenada().equals(Contexto.instance.getConfiguracionGeneralEC().getCoordenada());

					if (!mismaEc) {
						SuscriptorDeDatosExternos.instance.suscribirADatos(dato);
					}
				}
			}
		}
	}

	private Collection<Ejecutor> configurarEjecutores() {
		
		int cantProc  = 1;
		try {
			cantProc = Integer.parseInt(Contexto.instance.get("ec.modelo.matematico.exec.proc"));
		} catch (NumberFormatException e) {
			logger.warn("Error al leer la cantidad de procesadores. Usando valor por default");
		}

		List<Ejecutor> ejecutores = new ArrayList<Ejecutor>();
		for (int i = 0; i <  cantProc; i++) {
			EjecutorConcurrente ejec = new EjecutorConcurrente();
			ejec.init();
			ejecutores.add(ejec);
			
		}
		
		return ejecutores;
	}

	private void configurarModuloComunicacionTR() {
		logger.info("Creando modulo de comunicacion");
		
		ModuloComunicacionImpl moduloComunicacion = new ModuloComunicacionImpl();
		
		Protocolo protocolo = configurarProtocolo(moduloComunicacion);
		
		moduloComunicacion.setProtocolo(protocolo);
		
		Contexto.instance.setModuloComunicacion(moduloComunicacion);
	}

	private void configurarModuloComunicacionSC() {
		logger.info("Creando modulo de comunicacion con SC");

		ModuloComunicacionImpl comm = new ModuloComunicacionImpl();
		comm.registrarReceptor(new ReceptorSuscripcionDeDatos(), TipoMensaje.SUSCRIBIRSE_A_DATO);
		
		Protocolo protocoloSC = configurarProtocoloSC();
		
		comm.setProtocolo(protocoloSC);
		
		Contexto.instance.getProtocoloSC();
	}

	private Protocolo configurarProtocolo(ModuloComunicacion moduloComunicacion) {
		logger.info("Creando protocolo");
		long retryTime = Long.parseLong(Contexto.instance.get("ec.protocolo.steren.msg.retry"));
		long timeout = Long.parseLong(Contexto.instance.get("ec.protocolo.steren.msg.timeout"));
		SterenProtocol protocol = new SterenProtocol(moduloComunicacion, retryTime, timeout);
		
		Canal canalDeComunicacionTRs = crearCanal(protocol, "tr");
		
		
		protocol.setCanal(canalDeComunicacionTRs);
		
		return protocol;
	}

	private Canal crearCanal(Receptor receptor, String prefijo) {
		logger.info("Creando canales de comunicacion");
		CanalImpl canal = new CanalImpl();
		canal.setPuertoFactory(new PuertoArchivoFactory("TR"));
		canal.suscribirEntrada(Contexto.instance.getConfiguracionGeneralEC().getComponenteEc(), receptor);
		
		List<Componente> trs = Contexto.instance.getConfiguracionGeneralEC().getTrAdministradas();

		for (Componente tr : trs) {
			canal.suscribirSalida(tr);
		}

		logger.info(String.format("Terminales remotas suscriptas al canal: %s",
				Contexto.instance.getConfiguracionGeneralEC()
						.getTrAdministradas()));

		
		return canal;
	}
}
